//
// Created by hongyang.jia on 2023/6/5.
//

#include "eglTest.h"
//
#include "jni.h"
#include "errno.h"
#include "EGL/egl.h"
#include "GLES3/gl3.h"
#include <iostream>
#include <cassert>
//Android Native activity support

namespace Test {
#define PX_COUNTOF(x) (sizeof(x)/sizeof(x[0]))

    typedef struct _px_color {
        union {
            struct {
                unsigned char r;
                unsigned char g;
                unsigned char b;
                unsigned char a;
            };
            unsigned int ucolor;
        } _argb;
    } px_color;

    EGLDisplay display;
    EGLSurface surface;
    EGLContext context;

    GLuint vertexShader;
    GLuint pixelsShader;
    GLuint programObject;
    GLuint vPosition;
    GLuint vTex;
    GLuint renderTexture;
    int32_t width;
    int32_t height;


    static const char PX_AndroidEngine_GL_VS[] = "attribute vec4 vPosition;"
                                                 "attribute vec4 vTex;"
                                                 "varying vec2 textureCoordinate;"
                                                 "void main(){"
                                                 "gl_Position = vPosition;"
                                                 "textureCoordinate = vTex.xy;"
                                                 "}";
    static const char PX_AndroidEngine_GL_PS[] = "precision mediump float;"
                                                 "varying highp vec2 textureCoordinate;"
                                                 "uniform sampler2D tex;"
                                                 "void main(){"
                                                 "gl_FragColor = texture2D(tex, textureCoordinate);"
                                                 "}";

    static const GLfloat PX_AndroidEngine_squareVertices[] = {
            -1.0f, -1.0f,
            1.0f, -1.0f,
            -1.0f, 1.0f,
            1.0f, 1.0f,
    };

    static const GLfloat PX_AndroidEngine_texturePosition[] = {
            0.0f, 1.0f,
            1.0f, 1.0f,
            0.0f, 0.0f,
            1.0f, 0.0f,
    };


    static GLuint LoadShader(GLenum type, const char *shaderSources) {
        GLuint shader;
        GLint compiled;

        shader = glCreateShader(type);
        if (shader == 0) return false;

        glShaderSource(shader, 1, &shaderSources, NULL);
        glCompileShader(shader);

        glGetShaderiv(shader, GL_COMPILE_STATUS, &compiled);

        if (!compiled) {
            glDeleteShader(shader);
            return false;
        }
        return shader;
    }

    bool InitializeDisplay() {
        const int attrib_list[] = {EGL_CONTEXT_CLIENT_VERSION, 2, EGL_NONE};
        const EGLint attribs[] = {
                EGL_SURFACE_TYPE, EGL_WINDOW_BIT,
                EGL_BLUE_SIZE, 8,
                EGL_GREEN_SIZE, 8,
                EGL_RED_SIZE, 8,
                EGL_NONE
        };
        EGLint w, h, format;
        EGLint numConfigs;
        EGLConfig config;
        EGLConfig ConfigsDesc[64];
        int i;
        GLint linked;
        display = eglGetDisplay(EGL_DEFAULT_DISPLAY);


        eglInitialize(display, 0, 0);
        eglChooseConfig(display, attribs, NULL, 0, &numConfigs);
        if (numConfigs > PX_COUNTOF(ConfigsDesc)) {
            numConfigs = PX_COUNTOF(ConfigsDesc);
        }
        eglChooseConfig(display, attribs, ConfigsDesc, numConfigs, &numConfigs);

        for (i = 0; i < numConfigs; i++) {
            EGLint r, g, b, d, a;
            if (eglGetConfigAttrib(display, ConfigsDesc[i], EGL_RED_SIZE, &r) &&
                eglGetConfigAttrib(display, ConfigsDesc[i], EGL_GREEN_SIZE, &g) &&
                eglGetConfigAttrib(display, ConfigsDesc[i], EGL_BLUE_SIZE, &b) &&
                eglGetConfigAttrib(display, ConfigsDesc[i], EGL_ALPHA_SIZE, &a) &&
                eglGetConfigAttrib(display, ConfigsDesc[i], EGL_DEPTH_SIZE, &d) &&
                r == 8 && g == 8 && b == 8 && a == 8 && d == 0) {
                config = ConfigsDesc[i];
                break;
            }
        }

        if (i == numConfigs) {
            config = ConfigsDesc[0];
        }

        /* EGL_NATIVE_VISUAL_ID is an attribute of the EGLConfig that is
         * guaranteed to be accepted by ANativeWindow_setBuffersGeometry().
         * As soon as we picked a EGLConfig, we can safely reconfigure the
         * ANativeWindow buffers to match, using EGL_NATIVE_VISUAL_ID. */
        eglGetConfigAttrib(display, config, EGL_NATIVE_VISUAL_ID, &format);
        int surfaceAttribs[] = {
                EGL_WIDTH, width,
                EGL_HEIGHT, height,
                EGL_NONE
        };
        surface = eglCreatePbufferSurface(display, config, surfaceAttribs);
        assert(surface != NULL);
        if (surface == NULL) {
//        LOGE("Surface was null");
            return NULL;
        }
        context = eglCreateContext(display, config, EGL_NO_CONTEXT, attrib_list);

        if (eglMakeCurrent(display, surface, surface, context) == EGL_FALSE) {
            return false;
        }

        eglQuerySurface(display, surface, EGL_WIDTH, &w);
        eglQuerySurface(display, surface, EGL_HEIGHT, &h);

        display = display;
        context = context;
        surface = surface;
        width = w;
        height = h;

        // Initialize GL state.
        glEnable(GL_CULL_FACE);
        glEnable(GL_TEXTURE_2D);
        glEnable(GL_TEXTURE0);
        glEnable(GL_TEXTURE1);
        glDisable(GL_DEPTH_TEST);

        //Initialize GL shader
        vertexShader = LoadShader(GL_VERTEX_SHADER, PX_AndroidEngine_GL_VS);
        if (vertexShader == 0)
            return false;
        pixelsShader = LoadShader(GL_FRAGMENT_SHADER, PX_AndroidEngine_GL_PS);
        if (pixelsShader == 0)
            return false;

        programObject = glCreateProgram();
        if (programObject == 0)
            return false;

        glAttachShader(programObject, vertexShader);
        glAttachShader(programObject, pixelsShader);

        glLinkProgram(programObject);

        glGetProgramiv(programObject, GL_LINK_STATUS, &linked);
        if (!linked) {
            glDeleteProgram(programObject);
            return false;
        }

        glGenTextures(1, &renderTexture);

        vPosition = glGetAttribLocation(programObject, "vPosition");
        vTex = glGetAttribLocation(programObject, "vTex");

        glViewport(0, 0, width, height);


        return true;
    }

    bool FreeDisplay() {
        if (display != EGL_NO_DISPLAY) {
            eglMakeCurrent(display, EGL_NO_SURFACE, EGL_NO_SURFACE, EGL_NO_CONTEXT);
            if (context != EGL_NO_CONTEXT) {
                eglDestroyContext(display, context);
            }
            if (surface != EGL_NO_SURFACE) {
                eglDestroySurface(display, surface);
            }
            eglTerminate(display);
        }

        display = EGL_NO_DISPLAY;
        context = EGL_NO_CONTEXT;
        surface = EGL_NO_SURFACE;

        glDetachShader(programObject, vertexShader);
        glDetachShader(programObject, pixelsShader);
        glDeleteShader(pixelsShader);
        glDeleteShader(vertexShader);
        glDeleteProgram(programObject);

        return true;
    }

    void Engine_Render(unsigned char *buffer) {
        if (display == NULL) {
            // No display.
            return;
        }

        //vertex data
//    glClearColor(1.0f,0.0f,0.0f,1.0f);
//    glClear (GL_DEPTH_BUFFER_BIT | GL_COLOR_BUFFER_BIT);

        glUseProgram(programObject);

        glActiveTexture(GL_TEXTURE0);
        glBindTexture(GL_TEXTURE_2D, renderTexture);
        glTexImage2D(GL_TEXTURE_2D, \
    0, \
    GL_RGBA, \
    width, \
    height, \
    0, \
    GL_RGBA, \
    GL_UNSIGNED_BYTE, \
    buffer\
);
        glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
        glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
        glUniform1i(glGetAttribLocation(programObject, "tex"), 0);

        glVertexAttribPointer(vPosition, 2, GL_FLOAT, GL_FALSE, 0, PX_AndroidEngine_squareVertices);
        glEnableVertexAttribArray(vPosition);
        glDrawArrays(GL_TRIANGLE_STRIP, 0, 4);

        glVertexAttribPointer(vTex, 2, GL_FLOAT, 0, 0, PX_AndroidEngine_texturePosition);
        glEnableVertexAttribArray(vTex);

//    glViewport(0, 0, width , height);
//    glBindFramebuffer(GL_FRAMEBUFFER, 0);
//    unsigned int framebuffer;
//    glGenFramebuffers(1,&framebuffer);
//    glBindFramebuffer(GL_FRAMEBUFFER,framebuffer);
//    glClearColor(0.0f,1.0f,0.0f,1.0f);
//    glClear (GL_DEPTH_BUFFER_BIT | GL_COLOR_BUFFER_BIT);
//
//    glBindTexture(GL_TEXTURE_2D, renderTexture);
//    glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, renderTexture, 0);
////    glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, width, height, 0, GL_RGBA, GL_UNSIGNED_BYTE, nullptr);
//
////    glActiveTexture(GL_TEXTURE0);
////    glBindTexture(GL_TEXTURE_2D, renderTexture);
////////    glCopyTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, 0, 0, width, height, 0);
////    glCopyTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0,
////                        0, 0,width, height);
////    glBindTexture(GL_TEXTURE_2D, 0);
////    glBindFramebuffer(GL_FRAMEBUFFER, 0);


        eglSwapBuffers(display, surface);
    }



    void eglTest::egltest() {
        InitializeDisplay();
        unsigned int *mysurface = (unsigned int *) malloc(height * width * sizeof(unsigned int));;
        for (int i = 0; i < height; i++)
            for (int j = 0; j < width; j++) {
                mysurface[j * width + i] = i % 255;
            }

        Engine_Render((unsigned char *) mysurface);
        free(mysurface);

    }
}